package com.xpec.c4.codec.h2o;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class H2oDecoder {
	
	private static final Log log = LogFactory.getLog(H2oDecoder.class);

	public static Object decode(byte[] bytes) throws Exception {
		if (bytes == null || bytes.length == 0) {
			throw new Exception("bytes is null !");
		}

		return decode(new H2oBuffer(bytes));
	}
	
	public static Object decode(ByteBuffer buffer) throws Exception {
		if (buffer == null) {
			throw new Exception("ByteBuffer is null !");
		}
		
		byte[] bytes = new byte[buffer.limit()];
		buffer.get(bytes);
		
		return decode(bytes);
	}

	private static Object decode(H2oBuffer buffer) throws Exception {
		byte head = H2oInputStream.readNoVarintByte(buffer);

 		switch (head) {
		case CodedInfo.TYPE_NULL:
		case CodedInfo.TYPE_BOOLEAN:
		case CodedInfo.TYPE_BYTE:
		case CodedInfo.TYPE_CHAR:
		case CodedInfo.TYPE_SHORT:
		case CodedInfo.TYPE_INT:
		case CodedInfo.TYPE_LONG:
		case CodedInfo.TYPE_FLOAT:
		case CodedInfo.TYPE_DOUBLE:
		case CodedInfo.TYPE_STRING:
		case CodedInfo.TYPE_ENUM:
		case CodedInfo.TYPE_ARRAY:
		case CodedInfo.TYPE_COLLECTION:
		case CodedInfo.TYPE_MAP:
		case CodedInfo.TYPE_CUSTOM:
		case CodedInfo.TYPE_BIGDECIMAL:
			return decode(buffer, head, null);
		default:
			throw new Exception("unknown header");
		}
	}

	private static Object decode(H2oBuffer buffer, int type, Class fieldType) throws Exception {
		switch (type) {
		case CodedInfo.TYPE_NULL:
			return null;
		case CodedInfo.TYPE_BOOLEAN:
			return H2oInputStream.readBoolean(buffer);
		case CodedInfo.TYPE_BYTE:
			return H2oInputStream.readByte(buffer);
		case CodedInfo.TYPE_CHAR:
			return H2oInputStream.readChar(buffer);
		case CodedInfo.TYPE_SHORT:
			return H2oInputStream.readShort(buffer);
		case CodedInfo.TYPE_INT:
			return (int) H2oInputStream.readRawVarint(buffer);
		case CodedInfo.TYPE_LONG:
			return H2oInputStream.readRawVarint(buffer);
		case CodedInfo.TYPE_FLOAT:
			return H2oInputStream.readFloat(buffer);
		case CodedInfo.TYPE_DOUBLE:
			return H2oInputStream.readDouble(buffer);
		case CodedInfo.TYPE_STRING:
			return H2oInputStream.readString(buffer);
		case CodedInfo.TYPE_ENUM:
			return decodeEnum(buffer);
		case CodedInfo.TYPE_ARRAY:
			return decodeArray(buffer,fieldType);
		case CodedInfo.TYPE_COLLECTION:
			return decodeCollection(buffer,fieldType);
		case CodedInfo.TYPE_MAP:
			return decodeMap(buffer,fieldType);
		case CodedInfo.TYPE_BIGDECIMAL:
			return readBigDecimal(buffer);
		case CodedInfo.TYPE_CUSTOM:
			return decodeCustom(buffer,fieldType);
		default:
			throw new Exception("un define collection type:" + type);
		}
	}

	//decode readBigDecimal
	protected static BigDecimal readBigDecimal(H2oBuffer buffer) throws Exception {
		String decimalString = H2oInputStream.readString(buffer);
		BigDecimal bigDecimal = new BigDecimal(decimalString);
		return bigDecimal;
	}

	/**
	 * as没有enum类型，所以只解出字符串
	 *
	 *  @param buffer
	 *  @return
	 *  @throws Exception
	 */
	private static Object decodeEnum(H2oBuffer buffer) throws Exception {
//		int number = (int) H2oInputStream.readRawVarint(buffer);
		String name = H2oInputStream.readString(buffer);

//		Class ctype = CodedInfo.getRegisteredClass(number);

//		if (ctype == null) {
//			throw new Exception("un registered Enum:" + name);
//		}

//		return Enum.valueOf(ctype, name);
		return name;
	}

	private static Object decodeArray(H2oBuffer buffer, Class fieldType) throws Exception {
		int size = (int) H2oInputStream.readRawVarint(buffer);

		if (size == 0) {
			return null;
		}

		// 数组类型
		int arrayType = (int) H2oInputStream.readRawVarint(buffer);
		
		Class clz = null;
		switch (arrayType) {
		case CodedInfo.TYPE_COLLECTION:{
			clz = ArrayList.class;
			break;
		}
		case CodedInfo.TYPE_MAP:{
			clz = HashMap.class;
			break;
		}
		case CodedInfo.TYPE_ENUM:{
			clz = Enum.class;
			break;
		}
		case CodedInfo.TYPE_CUSTOM:{
			// 读取类编号
			int num = (int) H2oInputStream.readRawVarint(buffer);
			
			clz = CodedInfo.getRegisteredClass(num);
			break;
		}
		case CodedInfo.TYPE_NULL:{
			log.warn("array class type is null!!!");
			
			if(fieldType != null){
				if(fieldType.isArray()){
					clz = fieldType.getComponentType();
				}else if(List.class.isAssignableFrom(fieldType)){
					clz = Object.class;
				}
			}
			if(clz == null){
				throw new Exception("array class type is null!!!");
			}
		}
		default:{
			clz = CodedInfo.classTypeMapping.get(arrayType);
			if (clz == null) {
				if(fieldType != null){
					if(fieldType.isArray()){
						clz = fieldType.getComponentType();
					}else if(List.class.isAssignableFrom(fieldType)){
						clz = Object.class;
					}
				}
			}
			if(clz == null){
				throw new Exception("unknow array class type:" + arrayType);
			}
			break;
		}
		}
		
		//读取维度
		int dimensions = (int) H2oInputStream.readRawVarint(buffer);
		int[] dimensionsList = new int[dimensions];
		for (int i = 0; i < dimensions; i++) {
			dimensionsList[i] = (int) H2oInputStream.readRawVarint(buffer);
		}
		
		int realsize = (int) H2oInputStream.readRawVarint(buffer);

		Object array = Array.newInstance(clz, dimensionsList);

		for (int i = 0; i < realsize; i++) {
			int wtag = (int) H2oInputStream.readRawVarint(buffer);
			int wtype = WireFormat.getTagWireType(wtag);

			Object value = decode(buffer, wtype, fieldType);

			if (value != null) {
				int widx = WireFormat.getTagFieldNumber(wtag);
				insertArrayObject(array,value,dimensionsList,widx);
			}
		}

		return array;
	}

	private static Object decodeCollection(H2oBuffer buffer,Class fieldType) throws Exception {
		int size = (int) H2oInputStream.readRawVarint(buffer);

		ArrayList resultList = new ArrayList(size);
		
		if (size == 0) {
			return resultList;
		}

		int realsize = (int) H2oInputStream.readRawVarint(buffer);

		if (realsize == 0) {
			return resultList;
		}

		int wtag = (int) H2oInputStream.readRawVarint(buffer);
		int wtype = WireFormat.getTagWireType(wtag);
		int widx = WireFormat.getTagFieldNumber(wtag);
		for (int i = 0; i < size; i++) {
			if (i != widx) {
				resultList.add(null);
				continue;
			}

			resultList.add(decode(buffer, wtype,fieldType));

			--realsize;
			if (realsize == 0) {
				break;
			}

			wtag = (int) H2oInputStream.readRawVarint(buffer);
			widx = WireFormat.getTagFieldNumber(wtag);
			wtype = WireFormat.getTagWireType(wtag);
		}

		return resultList;
	}

	private static Object decodeMap(H2oBuffer buffer,Class fieldType) throws Exception {
		int size = (int) H2oInputStream.readRawVarint(buffer);

//		if (size == 0) {
//			return null;
//		}

		HashMap map = new HashMap();
		for (int i = 0; i < size; i++) {
			int keyType = (int) H2oInputStream.readRawVarint(buffer);

			Object objkey = decode(buffer, keyType,fieldType);

			int valueType = (int) H2oInputStream.readRawVarint(buffer);

			Object objvalue = decode(buffer, valueType,fieldType);

			map.put(objkey, objvalue);
		}

		return map;
	}

	private static Object decodeCustom(H2oBuffer buffer,Class fieldType) throws Exception {
		// 读取类编号
		int num = (int) H2oInputStream.readRawVarint(buffer);

		// 实际大小
		int realsize = (int) H2oInputStream.readRawVarint(buffer);

		Class<?> clz = CodedInfo.getRegisteredClass(num);
		Object object = clz.newInstance();
		
		if (realsize == 0) {
			return object;
		}

		Field[] fields = CodedInfo.getClassFields(clz);

		int lastTag = (int) H2oInputStream.readRawVarint(buffer);
		int wfield = WireFormat.getTagFieldNumber(lastTag);
		for (int i = 0; i < fields.length; i++) {
			if (i != wfield) {
				continue;
			}

			int wtype = WireFormat.getTagWireType(lastTag);
			Object tempObj = null;
			try {
				tempObj = decode(buffer, wtype,fields[i].getType());
			} catch (Exception e) {
				log.error("can not decoder "+clz+" field:"+fields[i]);
				throw e;
			}

			if (tempObj != null) {
				Class ftype = fields[i].getType();
				if(ftype.isEnum()){
					tempObj = Enum.valueOf(ftype, (String)tempObj);
				}else if(ftype.isArray()){
					if(tempObj instanceof List){
						List tempList = (List)tempObj;
						tempObj = Array.newInstance(ftype,  tempList.size());
						for (int j = 0; j < tempList.size(); j++) {
							Array.set(tempObj, j, tempList.get(j));
						}
					}else if(ftype.getComponentType().isPrimitive()){
						int size = Array.getLength(tempObj);
						tempObj = Array.newInstance(ftype.getComponentType(),  size);
						for (int j = 0; j < size; j++) {
							Array.set(tempObj, j, Array.get(tempObj, j));
						}
					}
				}else if(List.class.isAssignableFrom(ftype)){
					if(tempObj.getClass().isArray()){
						List tempList=new ArrayList();
						int size = Array.getLength(tempObj);
						for (int j = 0; j < size; j++) {
							tempList.add(Array.get(tempObj, j));
						}
						tempObj=tempList;
					}
				}else{
					while(ftype.isArray()){
						ftype = ftype.getComponentType();
					}
					if (fields[i].getType().isArray()) {
						if (ftype.isPrimitive()) {
							tempObj = toPrimitiveArray(tempObj, fields[i].getType().getComponentType());
						}
					}
				}

				fields[i].set(object, transToFieldObject(fields[i],wtype,tempObj));
			}

			--realsize;
			if (realsize == 0) {
				break;
			}

			lastTag = (int) H2oInputStream.readRawVarint(buffer);
			wfield = WireFormat.getTagFieldNumber(lastTag);
		}

		return object;
	}

	private static Object toPrimitiveArray(Object object, Class<?> toClz) throws Exception {
		int size = Array.getLength(object);

		Object toArray = Array.newInstance(toClz, size);
		for (int i = 0; i < size; i++) {
			Object value = Array.get(object, i);
			
			if(value==null){
				continue;
			}
			
			Class<?> clz = value.getClass();
			if(clz.isArray()){
				value = toPrimitiveArray(value, toClz.getComponentType());
			}
			
			Array.set(toArray, i, value);
		}

		return toArray;
	}
	
	private static void insertArrayObject(Object array, Object object, int[] dimensionsList, int index) throws Exception {
		int[] idx = new int[dimensionsList.length];
		for (int i = idx.length-1; i >= 0 ; i--) {
			idx[i] = index%dimensionsList[i];
			index /= dimensionsList[i];
		}
		
		for (int i = 0; i < idx.length - 1; i++) {
			array = Array.get(array, idx[i]);
		}
		Array.set(array, idx[idx.length - 1], object);
	}
	
	/**
	 * 将对象值转变为字段需要的值
	 *
	 *  @param field
	 *  @param objType
	 *  @param obj
	 *  @return
	 *  @throws Exception
	 */
	private static Object transToFieldObject(Field field, int objType, Object obj) throws Exception{
		if(!field.getType().isPrimitive()){
			return obj;
		}
		int fieldType = CodedInfo.typeClassMapping.get(CodedInfo.primitiveToObject(field.getType()));
		if(objType != fieldType){
			if(field.getType() == byte.class){
				return (byte)objectToLong(objType,obj);
			}else if(field.getType() == char.class){
				return (char)objectToLong(objType,obj);
			}else if(field.getType() == short.class){
				return (short)objectToLong(objType,obj);
			}else if(field.getType() == int.class){
				return (int)objectToLong(objType,obj);
			}else if(field.getType() == long.class){
				return (long)objectToLong(objType,obj);
			}else if(field.getType() == float.class){
				return (float)objectToDouble(objType,obj);
			}else if(field.getType() == double.class){
				return (double)objectToDouble(objType,obj);
			}
		}
		return obj;
	}

	private static double objectToDouble(int objType, Object obj) throws Exception{
		if(objType == CodedInfo.TYPE_FLOAT ){
			return (Float)obj;
		}else if(objType == CodedInfo.TYPE_DOUBLE){
			return (Double)obj;
		}else{
			throw new Exception(obj + " can not change double type:"+objType);
		}
	}
	
	private static long objectToLong(int objType, Object obj) throws Exception{
		if(objType == CodedInfo.TYPE_BYTE ){
			return (Byte)obj;
		}else if(objType == CodedInfo.TYPE_CHAR){
			return (Character)obj;
		}else if(objType == CodedInfo.TYPE_SHORT){
			return (Short)obj;
		}else if(objType == CodedInfo.TYPE_INT){
			return (Integer)obj;
		}else if(objType == CodedInfo.TYPE_LONG){
			return (Long)obj;
		}else{
			throw new Exception(obj + " can not change long type:"+objType);
		}
	}
	
	public static void main(String[] aaa) {
		int[] dimensionsList = new int[]{3,3,4};
		int index = 25;
		
		int[] idx = new int[dimensionsList.length];
		for (int i = idx.length-1; i >= 0 ; i--) {
			idx[i] = index%dimensionsList[i];
			index /= dimensionsList[i];
		}

		for (int i = 0; i < idx.length; i++) {
			System.out.println(idx[i]);
		}
	}
}
